<html>

    <head>
        <title>WebSockets Latency Test</title>
        <script src="include/base64.js"></script>
        <script src="include/util.js"></script>
        <script src="include/webutil.js"></script> 
        <script src="include/websock.js"></script> 
        <!-- Uncomment to activate firebug lite -->
        <!--
        <script type='text/javascript' 
            src='http://getfirebug.com/releases/lite/1.2/firebug-lite-compressed.js'></script>
        -->


    </head>

    <body>

        Host: <input id='host' style='width:100'>&nbsp;
        Port: <input id='port' style='width:50'>&nbsp;
        Encrypt: <input id='encrypt' type='checkbox'>
        <br>
        Payload Size: <input id='payload_size' style='width:50'>&nbsp;
        Send Delay (ms): <input id='sendDelay' style='width:50' value="10">&nbsp;
        <input id='connectButton' type='button' value='Start' style='width:100px'
            onclick="connect();">&nbsp;

        <br><br>
        <table border=1>
            <tr>
                <th align="right">Packets sent:</th>
                <td align="right"><div id='sent'></div></td>
            </tr><tr>
                <th align="right">Packets Received:</th>
                <td align="right"><div id='received'></div></td>
            </tr><tr>
                <th align="right">Average Latency:</th>
                <td align="right"><div id='laverage'></div></td>
            </tr><tr>
                <th align="right">40 Frame Running Average Latency:</th>
                <td align="right"><div id='lrunning'></div></td>
            </tr><tr>
                <th align="right">Minimum Latency:</th>
                <td align="right"><div id='lmin'></div></td>
            </tr><tr>
                <th align="right">Maximum Latency:</th>
                <td align="right"><div id='lmax'></div></td>
            </tr>
        </table>

        <br>
        Messages:<br>
        <textarea id="messages" style="font-size: 9;" cols=80 rows=10></textarea>
    </body>


    <script>

        var host = null, port = null, sendDelay = 0, actualSendDelay,
            ws = null, send_ref = null,
            sent, received, latencies, ltotal, laverage, lrunning, lmin, lmax,
            run_length = 40,
            payload_size = 2000, payload,
            msg_cnt = 0, recv_seq = 0, send_seq = 0;

        Array.prototype.pushStr = function (str) {
            var n = str.length;
            for (var i=0; i < n; i++) {
                this.push(str.charCodeAt(i));
            }
        }


        function message(str) {
            console.log(str);
            cell = $D('messages');
            msg_cnt++;
            cell.innerHTML += msg_cnt + ": " + str + "\n";
            cell.scrollTop = cell.scrollHeight;
        }


        function add (x,y) {
            return parseInt(x,10)+parseInt(y,10);
        }

        function recvMsg(data) {
            //console.log(">> check_respond");
            var i, now, arr, first, last, arr, latency;

            now = (new Date()).getTime(); // Early as possible

            arr = ws.rQshiftBytes(ws.rQlen());
            first = String.fromCharCode(arr.shift());
            last = String.fromCharCode(arr.pop());

            if (first != "^") {
                message("Error: packet missing start char '^'");
                disconnect();
                return;
            }
            if (last != "$") {
                message("Error: packet missing end char '$'");
                disconnect();
                return;
            }
            arr = arr.map(function(num) {
                    return String.fromCharCode(num); 
                } ).join('').split(':');
            seq       = arr[0];
            timestamp = parseInt(arr[1],10);
            rpayload  = arr[2];

            if (seq != recv_seq) {
                message("Error: expected seq " + recv_seq + " but got " + seq);
                disconnect();
                return;
            }
            recv_seq++;
            if (payload !== rpayload) {
                message("Payload corrupt");
                disconnect();
                return;
            }

            received++;

            latency = now - timestamp;
            latencies.push(latency);
            if (latencies.length > run_length) {
                latencies.shift();
            }
            ltotal   += latency;
            laverage  = ltotal / received;
            lrunning  = 0;
            for (var i=0; i < latencies.length; i++) {
                lrunning += latencies[i];
            }
            lrunning  = lrunning / latencies.length;

            if (latency < lmin) {
                lmin  = latency;
            }
            if (latency > lmax) {
                lmax  = latency;
            }

            showStats();
            //console.log("<< check_respond");
        }

        function sendMsg() {
            var arr = [];
            if (! ws.flush() ) {
                message("WebSocket not ready, backing off");
                actualSendDelay = actualSendDelay * 2;
                send_ref = setTimeout(sendMsg, actualSendDelay);
                return false;
            } else {
                // Scale the delay down to the requested minimum
                if (actualSendDelay > sendDelay) {
                    message("WebSocket ready, increasing presure");
                    actualSendDelay = Math.max(actualSendDelay / 2, sendDelay);
                }
            }

            timestamp = (new Date()).getTime();
            arr.pushStr("^" + send_seq + ":" + timestamp + ":" + payload + "$");
            send_seq ++;
            ws.send(arr);
            sent++;

            showStats();
            send_ref = setTimeout(sendMsg, actualSendDelay);
        }

        function showStats() {
            $D('sent').innerHTML     = sent;
            $D('received').innerHTML = received;
            $D('laverage').innerHTML = laverage.toFixed(2);
            $D('lrunning').innerHTML = lrunning.toFixed(2);
            $D('lmin').innerHTML     = lmin.toFixed(2);
            $D('lmax').innerHTML     = lmax.toFixed(2);
        }

        function init_ws() {
            console.log(">> init_ws");
            var scheme = "ws://";
            if ($D('encrypt').checked) {
                scheme = "wss://";
            }
            var uri = scheme + host + ":" + port;
            console.log("connecting to " + uri);
            ws = new Websock();
            ws.maxBufferedAmount = 5000;
            ws.open(uri);

            ws.on('message', function() {
                recvMsg();
            });
            ws.on('open', function() {
                send_ref = setTimeout(sendMsg, sendDelay);
            });
            ws.on('close', function(e) {
                disconnect();
            });
            ws.on('error', function(e) {
                message("Websock error: " + e);
                disconnect();
            });

            console.log("<< init_ws");
        }

        function connect() {
            console.log(">> connect");
            host = $D('host').value;
            port = $D('port').value;
            payload_size = parseInt($D('payload_size').value, 10);
            sendDelay = parseInt($D('sendDelay').value, 10);

            if ((!host) || (!port)) {
                console.log("must set host and port");
                return;
            }

            if (ws) {
                ws.close();
            }
            init_ws();

            // Populate payload data
            var numlist = []
            for (var i=0; i < payload_size; i++) {
                numlist.push( Math.floor(Math.random()*10) );
            }
            payload = numlist.join('');

            // Initialize stats
            sent      = 0;
            received  = 0;
            latencies = [];
            ltotal    = 0;
            laverage  = 0;
            lrunning  = 0;
            lmin      = 999999999;
            lmax      = 0;
            actualSendDelay = sendDelay;

            $D('connectButton').value = "Stop";
            $D('connectButton').onclick = disconnect;
            console.log("<< connect");
        }

        function disconnect() {
            console.log(">> disconnect");
            if (ws) {
                ws.close();
            }

            if (send_ref) {
                clearInterval(send_ref);
                send_ref = null;
            }
            showStats(); // Final numbers
            recv_seq = 0;
            send_seq = 0;

            $D('connectButton').value = "Start";
            $D('connectButton').onclick = connect;
            console.log("<< disconnect");
        }


        window.onload = function() {
            console.log("onload");
            if (Websock_native) {
                message("Using native WebSockets");
            } else {
                message("initializing web-socket-js flash bridge");
            }
            var url = document.location.href;
            $D('host').value = (url.match(/host=([^&#]*)/) || ['',window.location.hostname])[1];
            $D('port').value = (url.match(/port=([^&#]*)/) || ['',window.location.port])[1];
            $D('payload_size').value = payload_size;
        }
    </script>

</html>
